上篇簡單講述了閉包的特性之後,今天來點不同的舉例來加深印象!
先來看看以下程式碼:
function createFunctionArray() {
var functionArray = []; // 創建一個函式陣列,用於存放多個函式
for (var i = 0; i < 5; i++) {
// 在迴圈中,我們創建一個匿名函式,該函式將輸出目前的索引值
functionArray.push(
function printIndex() {
console.log(i);
}
);
}
return functionArray;
}
var functions = createFunctionArray(); // 創建一個包含多個函式的陣列
functions[0](); // 呼叫陣列中的第一個函式,將輸出 5
functions[1](); // 呼叫陣列中的第二個函式,將輸出 5
5
?回想一下閉包的特性,雖然上面的程式碼似乎應該依次輸出 0
、1
,
但實際上它們都輸出 5
的原因是因為在迴圈中創建的匿名函式捕獲了變數 i
的引用,而不是其值。
在 createFunctionArray
函式中,我們創建了一個函式陣列 functionArray
,
並使用一個迴圈來添加匿名函式到這個陣列中。這些匿名函式都捕獲了外部作用域的變數 i
。
當迴圈完成並退出後,i
的值等於 5
,因為這是使迴圈停止的條件。
由於這些匿名函式仍然引用相同的 i
,當呼叫這些函式時,都將使用當前的 i
值,即 5
。
這就是為什麼無論呼叫 functions[0]()
還是 functions[1]()
,都輸出 5
的原因。
0
、1
呢?我們可以在每次迴圈迭代時創建一個新的作用域,這樣每個匿名函式都會捕獲不同的 i
值。
let
來產生區塊作用域
function createFunctionArray() {
var functionArray = [];
for (let i = 0; i < 5; i++) { // 使用 let 創建區塊作用域
functionArray.push(function() {
console.log(i);
});
}
return functionArray;
}
var functions = createFunctionArray();
functions[0](); // 輸出 0
functions[1](); // 輸出 1
使用 let
創建的 i
變數在每次迴圈迭代時都有自己的區塊作用域,
因此每個匿名函式都能正確地捕獲到其自己的 i
值。
function createFunctionArray() {
var functionArray = [];
for (var i = 0; i < 5; i++) {
(function (index) { // 使用IIFE創建新的作用域
functionArray.push(function() {
console.log(index);
});
})(i); // 將i作為參數傳遞給IIFE
}
return functionArray;
}
var functions = createFunctionArray();
functions[0](); // 輸出 0
functions[1](); // 輸出 1
這樣做之後,每個匿名函式都捕獲了它自己的 index
值,使得輸出正確。
IIFE 通常用於
- 創建私有作用域:
IIFE 創建了一個獨立的作用域,其中的變數在函式執行後會被銷毀。
這有助於防止變數污染全域作用域。- 模組化程式碼:
IIFE 可用於創建模組化的程式碼區塊,其中可以定義私有變數和函式,
並通過返回公共接口來封裝它們,以供外部使用。
封裝私有變數和函式:
閉包可以用來創建具有私有變數的函式,以增加安全性。
function createCounter() {
let count = 0;
return {
increment: function () {
count++;
},
getCount: function () {
return count;
}
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 輸出 1
事件處理程序:
閉包可用於處理事件。當使用者點擊按鈕時,可以使用閉包來記錄點擊次數。
const button = document.getElementById('likeButton');
let clickCount = 0;
button.addEventListener('click', function () {
clickCount++;
console.log(`點擊次數:${clickCount}`);
});
setTimeout 和 setInterval:
使用閉包可以創建具有狀態的定時任務。
function createTimer() {
let seconds = 0;
return function () {
seconds++;
console.log(`過去秒數:${seconds}`);
};
}
const timer = createTimer();
setInterval(timer, 1000);
模組模式:
閉包可用於創建模組,將相關的函式和數據封裝在一起,以提供更好的代碼組織。
假設我們正在建立一個計數器模組:
const CounterModule = (function () {
// 私有變數
let count = 0;
// 增加
function increment() {
count++;
}
// 減少
function decrement() {
count--;
}
// 獲取當前值
function getCount() {
return count;
}
// 公開的部分
return {
increment: increment,
decrement: decrement,
getCount: getCount
};
})();
// 使用計數器模組
CounterModule.increment();
CounterModule.increment();
console.log(CounterModule.getCount()); // 輸出 2
CounterModule.decrement();
console.log(CounterModule.getCount()); // 輸出 1
在這個例子中,CounterModule
是一個使用閉包實現的模組,
包含了私有變數 count
和三個公開方法:increment
、decrement
和 getCount
。
這樣,我們可以使用模組來管理計數器的狀態,同時保護了 count
變數,
使其無法被外部直接訪問或修改,提供了更好的代碼組織和隔離。
快取:
閉包可用於創建簡單的快取,以避免重複計算。
function createCache() {
const cache = {};
return function (key) {
if (key in cache) {
return cache[key];
} else {
const result = /* 計算結果 */;
cache[key] = result;
return result;
}
};
}
const getValue = createCache();
console.log(getValue('data')); // 計算並快取結果
console.log(getValue('data')); // 直接使用快取的結果
深入了解不同的例子與閉包實際應用之後,相信大家有對閉包留下深刻的印象!
今天就先分享到這,我們下篇見!
參考資料:
文章同步於個人部落格:Viiisit!(歡迎參觀 ୧ʕ•̀ᴥ•́ʔ୨)